%%%-------------------------------------------------------------------
%%% @author linhaibo
%%% @copyright (C) 2018, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 12. 三月 2018 上午11:50
%%%-------------------------------------------------------------------
-module(db_worker).
-author("linhaibo").

-behaviour(gen_server).

%% API
-export([start_link/0,token_get/1,token_set/2]).
-export([check_friend/2,add_friend/2,del_friend/2]).

%% gen_server callbacks
-export([init/1,
	handle_call/3,
	handle_cast/2,
	handle_info/2,
	terminate/2,
	code_change/3]).

-define(SERVER, ?MODULE).
-define(POOL_NAME,db_pool).
-define(APNS_TOKEN_BUCKET,<<"ApnsToken">>).
-define(RECONNECT_TIMEOUT,5000).
-define(FRIEND_BUCKET,<<"Friend">>).
-define(RIAK_HOST,"127.0.0.1").
-define(RIAK_PORT,8087).
-record(state, {pid}).

%%%===================================================================
%%% API
%%%===================================================================

%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
-spec(start_link() ->
	{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() -> gen_server:start_link(?MODULE, [], []).

token_get(Key) -> wpool:call(?POOL_NAME, {token_get, Key}, available_worker).
token_set(Key,Value) -> wpool:cast(?POOL_NAME, {token_set, Key , Value}, available_worker).
add_friend(UId,FId) -> wpool:call(?POOL_NAME, {add_friend, UId,FId}, available_worker).
del_friend(UId,FId) -> wpool:cast(?POOL_NAME, {del_friend, UId,FId}, available_worker).
check_friend(UId,FId) -> wpool:call(?POOL_NAME, {check_friend, UId,FId}, available_worker).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%%                     {ok, State, Timeout} |
%%                     ignore |
%%                     {stop, Reason}
%% @end
%%--------------------------------------------------------------------
-spec(init(Args :: term()) ->
	{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
	{stop, Reason :: term()} | ignore).
init([]) ->
	 case riakc_pb_socket:start(?RIAK_HOST, ?RIAK_PORT) of
		 {error, Error} ->
			 error_logger:info_msg("connect riak fail:~p",[Error]),
			 erlang:send_after(?RECONNECT_TIMEOUT,self(),{reconnect}),
			 {ok,#state{pid=undefined}};
		 {ok, PId} ->
			 error_logger:info_msg("connected to riak!"),
			 {ok, #state{pid=PId}}
	 end.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
	State :: #state{}) ->
	{reply, Reply :: term(), NewState :: #state{}} |
	{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
	{noreply, NewState :: #state{}} |
	{noreply, NewState :: #state{}, timeout() | hibernate} |
	{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
	{stop, Reason :: term(), NewState :: #state{}}).

handle_call({token_get,Key},_From, #state{pid=PId} = State) when is_binary(Key) and is_pid(PId) ->
	case riakc_pb_socket:get(PId,?APNS_TOKEN_BUCKET,Key) of
		{ok, Ret} ->
			TokenInfo = riakc_obj:get_value(Ret),
			{reply,TokenInfo,State};
		{error,_} ->
			{reply,false,State}
	end;

handle_call({add_friend,UId,FId},_From,#state{pid=PId} = State) when is_binary(UId)  and is_pid(PId) ->
	FriendList = case riakc_pb_socket:get(PId,?FRIEND_BUCKET,UId) of
		{ok, Ret} ->
			Bin = riakc_obj:get_value(Ret),
			binary_to_term(Bin);
		{error,notfound} ->
			[];
		{error,_} ->
			[]
	end,
	case lists:member(FId,FriendList) of
		true -> {reply,true,State};
		false ->
			NewFriendList = lists:append(FriendList,[FId]),
			NewObj = riakc_obj:new(?FRIEND_BUCKET,UId,term_to_binary(NewFriendList)),
			%error_logger:info_msg("newobj=~p",[NewObj]),
			riakc_pb_socket:put(PId,NewObj),
			{reply,true,State}
	end;


handle_call({check_friend,UId,FId},_From,#state{pid=PId} = State) when is_binary(UId)  and is_pid(PId) ->
	FriendList = case riakc_pb_socket:get(PId,?FRIEND_BUCKET,UId) of
					 {ok, Ret} ->
						 Bin = riakc_obj:get_value(Ret),
						 binary_to_term(Bin);
					 {error,notfound} ->
						 [];
					 {error,_} ->
						 []
				 end,
	case lists:member(FId,FriendList) of
		true -> {reply,true,State};
		false -> {reply,false,State}
	end;

handle_call(_Request, _From, State) ->
	{reply, false, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_cast(Request :: term(), State :: #state{}) ->
	{noreply, NewState :: #state{}} |
	{noreply, NewState :: #state{}, timeout() | hibernate} |
	{stop, Reason :: term(), NewState :: #state{}}).
handle_cast({token_set,Key,Value},#state{pid=PId} = State) when is_binary(Key) and is_binary(Value) and is_pid(PId) ->
	NewObj = riakc_obj:new(?APNS_TOKEN_BUCKET,Key,Value),
	riakc_pb_socket:put(PId,NewObj),
	%error_logger:info_msg("ret=~p",[NewObj]),
	{noreply, State};

handle_cast({del_friend,UId,FId},#state{pid=PId} = State) when is_binary(UId) ->
	FriendList = case riakc_pb_socket:get(PId,?FRIEND_BUCKET,UId) of
					 {ok, Ret} ->
						 Bin = riakc_obj:get_value(Ret),
						 binary_to_term(Bin);
					 {error,notfound} ->
						 [];
					 {error,_} ->
						 []
				 end,
	case lists:member(FId,FriendList) of
		true ->
			NewFriendList = lists:delete(FId,FriendList),
			NewObj = riakc_obj:new(?FRIEND_BUCKET,UId,term_to_binary(NewFriendList)),
			%error_logger:info_msg("newobj=~p",[NewObj]),
			riakc_pb_socket:put(PId,NewObj);
		false -> ok
	end,
	{noreply, State};
handle_cast(_Request, State) ->
	{noreply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%%                                   {noreply, State, Timeout} |
%%                                   {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
	{noreply, NewState :: #state{}} |
	{noreply, NewState :: #state{}, timeout() | hibernate} |
	{stop, Reason :: term(), NewState :: #state{}}).

handle_info({reconnect},#state{pid=undefined}) ->
	case riakc_pb_socket:start(?RIAK_HOST, ?RIAK_PORT) of
		{error, Error} ->
			error_logger:info_msg("connect riak fail:~p",[Error]),
			erlang:send_after(?RECONNECT_TIMEOUT,self(),{reconnect}),
			{noreply,#state{pid=undefined}};
		{ok, PId} ->
			error_logger:info_msg("connected to riak!"),
			{noreply, #state{pid=PId}}
	end;
handle_info(_Info, State) ->
	{noreply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
	State :: #state{}) -> term()).
terminate(_Reason, _State) ->
	ok.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
	Extra :: term()) ->
	{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State, _Extra) ->
	{ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================
